import 'dart:async';
import 'dart:convert';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:date_format/date_format.dart';
import 'package:audio_session/audio_session.dart';
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_sound/flutter_sound.dart';
import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import 'package:rongcloud_im_wrapper_plugin/rongcloud_im_wrapper_plugin.dart';
import 'package:zootix/constants/zt_constants.dart';
import 'package:zootix/models/zt_im_message.model.dart';
import 'package:zootix/screens/rank/zt_rank.controller.dart';
import 'package:zootix/services/zt_translate.service.dart';
import 'package:zootix/store/zt_im_chat.store.dart';
import 'package:zootix/store/zt_store.dart';
import 'package:zootix/util/zt_loading_util.dart';
import 'package:zootix/util/zt_logger_util.dart';
import 'package:zootix/util/zt_snack_bar.util.dart';
import 'package:zootix/zt_app_theme.dart';
import 'package:zootix/apis/zt-api.dart' as Api;
import 'package:flutter_sound_platform_interface/flutter_sound_recorder_platform_interface.dart';

import '../../util/zt_util.dart';

typedef _Fn = void Function();
const theSource = AudioSource.microphone;

class ZtPrivateChatController extends GetxController {
  final chatMessages = <ZtIMessageModel>[].obs;
  final textEditingController = TextEditingController();
  final scrollController = ScrollController();
  final refreshController = RefreshController();
  int? cursorPosition;
  AnimationController? micAnimationController;
  late bool isCustomerService;
  RxBool emojiShowing = RxBool(false);
  RxBool voiceShowing = RxBool(false);
  RxBool filePickerShowing = RxBool(false);
  RxBool isHoverOnCancel = RxBool(false);
  RxBool isHoverOnSend = RxBool(false);
  int page = 1;
  int limit = 30;
  int total = 0;
  final GlobalKey containerCancelKey = GlobalKey();
  final GlobalKey containerSendKey = GlobalKey();

  Codec _codec = Codec.aacMP4;
  Timer? _timer;
  Timer? _recordTimer;
  String _mPath = 'tau_file.mp4';
  RxBool isDrawing = RxBool(false);
  bool _mplaybackReady = false;
  FlutterSoundPlayer? mPlayer = FlutterSoundPlayer();
  FlutterSoundRecorder? mRecorder = FlutterSoundRecorder();
  RxString uploadFilePath = RxString('');
  int maxRecordTime = 60;
  int recordedTime = 0;
  RxBool mPlayerIsInited = RxBool(false);
  RxBool mRecorderIsInited = RxBool(false);
  String playingUrl = '';
  RxBool isLoadingVoice = RxBool(false);
  @override
  void onInit() async {
    initPlayer().then((value) => {mPlayerIsInited.value = true});
    initRecorder().then((value) => {mRecorderIsInited.value = true});
    ZtLogManger().putLog(
      ZtLogType.clickEvent,
      ZtLogManger.buildEvent(
          logType: ZtLogType.clickEvent, page: 'privatechat'),
    );
    super.onInit();
  }

  Future<void> initPlayer() async {
    await mPlayer!.openPlayer();
  }

  Future<void> initRecorder() async {
    await openTheRecorder();
  }

  @override
  void onReady() {
    super.onReady();

    onMessageReceived();
    getData(isReset: true);
  }

  @override
  void onClose() {
    super.onClose();
    ZtImChartStore.to.imEngine?.onMessageReceived = null;
    ZtImChartStore.to.onMessageReceived();
    mPlayer?.stopPlayer();
    mPlayer?.closePlayer();
    mPlayer = null;
    mRecorder?.stopRecorder();
    mRecorder?.closeRecorder();
    mRecorder = null;
    _timer?.cancel();
    _recordTimer?.cancel();
    textEditingController.dispose();
    refreshController.dispose();
    scrollController.dispose();
  }

  Future<void> playVoiceMessage(url) async {
    if (isLoadingVoice.value) return;
    _mplaybackReady = true;
    playingUrl = url;
    isLoadingVoice.value = true;
    update();
    await play(url: url);
  }

  _Fn? getRecorderFn() {
    if (!mRecorderIsInited.value || !mPlayer!.isStopped) {
      return null;
    }
    return mRecorder!.isStopped ? record : stopRecorder;
  }

  _Fn? getPlaybackFn() {
    if (!mPlayerIsInited.value || !_mplaybackReady || !mRecorder!.isStopped) {
      return null;
    }
    return mPlayer!.isStopped ? play : stopPlayer;
  }

  play({String? url}) async {
    if (url == null) {
      playingUrl = '';
    } else {
      isLoadingVoice.value = true;
    }
    assert(mPlayerIsInited.value &&
        _mplaybackReady &&
        mRecorder!.isStopped &&
        mPlayer!.isStopped);
    await mPlayer!.startPlayer(
        fromURI: url ?? _mPath,
        codec: Codec.mp3,
        sampleRate: 44000,
        whenFinished: () {
          update();
        });
    if (url != null) {
      isLoadingVoice.value = false;
    }
    update();
  }

  void stopPlayer() {
    if (mPlayer != null && mPlayer!.isPlaying) {
      mPlayer!.stopPlayer().then((value) {
        update();
      });
    }
  }

  void record() {
    recordedTime = 0;
    if (mRecorder != null) {
      mRecorder!
          .startRecorder(
        toFile: _mPath,
        codec: _codec,
        audioSource: theSource,
      )
          .then((value) {
        _timer = Timer(Duration(seconds: maxRecordTime), () {
          stopRecorder();
        });
        isDrawing.value = true;
        startDrawing();
        update();
        _recordTimer = Timer.periodic(Duration(seconds: 1), (timer) {
          recordedTime++;
        });
        /*  _recorderSubscription = mRecorder!.onProgress!.listen((e) {
          recordedTime = e.duration.inSeconds;
        }, onError: (e) {
          print(e);
        }); */
      });
    }
  }

  Future<void> stopRecorder({bool forceStop = false}) async {
    if (mRecorder!.isRecording) {
      String? value = await mRecorder!.stopRecorder();

      if (value != null) {
        //var url = value;
        if (!forceStop) {
          uploadFilePath.value = value;
        }
        _mplaybackReady = true;
        isDrawing.value = false;
        if (_timer != null) {
          _timer?.cancel();
        }
        if (_recordTimer != null) {
          _recordTimer?.cancel();
        }
        stopDrawing();
        update();
      }
    }
  }

  void startDrawing() {
    micAnimationController?.repeat();
  }

  void stopDrawing() {
    micAnimationController?.reset();
  }

  Future<void> openTheRecorder() async {
    if (!kIsWeb) {
      var status = await Permission.microphone.request();
      if (status != PermissionStatus.granted) {
        throw RecordingPermissionException('Microphone permission not granted');
      }
    }
    await mRecorder!.openRecorder();
    if (!await mRecorder!.isEncoderSupported(_codec) && kIsWeb) {
      _codec = Codec.opusWebM;
      _mPath = 'tau_file.webm';
      if (!await mRecorder!.isEncoderSupported(_codec) && kIsWeb) {
        mRecorderIsInited.value = true;
        return;
      }
    }
    final session = await AudioSession.instance;
    await session.configure(AudioSessionConfiguration(
      avAudioSessionCategory: AVAudioSessionCategory.playAndRecord,
      avAudioSessionCategoryOptions:
          AVAudioSessionCategoryOptions.allowBluetooth |
              AVAudioSessionCategoryOptions.defaultToSpeaker,
      avAudioSessionMode: AVAudioSessionMode.spokenAudio,
      avAudioSessionRouteSharingPolicy:
          AVAudioSessionRouteSharingPolicy.defaultPolicy,
      avAudioSessionSetActiveOptions: AVAudioSessionSetActiveOptions.none,
      androidAudioAttributes: const AndroidAudioAttributes(
        contentType: AndroidAudioContentType.speech,
        flags: AndroidAudioFlags.none,
        usage: AndroidAudioUsage.voiceCommunication,
      ),
      androidAudioFocusGainType: AndroidAudioFocusGainType.gain,
      androidWillPauseWhenDucked: true,
    ));

    mRecorderIsInited.value = true;
  }

  onMessageReceived() {
    ZtImChartStore.to.imEngine?.onMessageReceived = (RCIMIWMessage? message,
        int? left, bool? offline, bool? hasPackage) async {
      ZtLoggerUtil().info('[Controller Private chat] onMessageReceived}');
      if (message != null && message.senderUserId == Get.arguments['userId']) {
        ZtIMessageModel chatModel = ZtImChartStore.to.processImMessage(message);
        chatMessages.insert(0, chatModel);
        ZtImChartStore.to.getConversations(limit: 30);
      }
    };
  }

  onEmojiSelected(Emoji emoji) {
    if (cursorPosition != null) {
      String currentValue = textEditingController.text;
      String newValue = currentValue.substring(0, cursorPosition!) +
          emoji.emoji +
          currentValue.substring(cursorPosition!);
      textEditingController.value = TextEditingValue(
        text: newValue,
        selection: TextSelection.collapsed(
          offset: cursorPosition! + emoji.emoji.length,
        ),
      );
    } else {
      textEditingController
        ..text += emoji.emoji
        ..selection = TextSelection.fromPosition(
            TextPosition(offset: textEditingController.text.length));
    }
  }

  onBackspacePressed() {
    // emojiShowing.value = false;
    textEditingController
      ..text = textEditingController.text.characters.skipLast(1).toString()
      ..selection = TextSelection.fromPosition(
          TextPosition(offset: textEditingController.text.length));
  }

  Future<void> onRefresh() async {
    await getData().then((_) {
      if (_.isEmpty) {
        refreshController.loadNoData();
      } else {
        refreshController.loadComplete();
      }
    }).catchError((_) {
      refreshController.loadFailed();
    });
  }

  Future<List<ZtIMessageModel>> getData(
      {bool isReset = false, RCIMIWMessageOperationPolicy? policy}) async {
    if (!isReset) {
      await Future.delayed(const Duration(milliseconds: 500));
    }
    int sendTime = DateTime.now().millisecondsSinceEpoch;
    if (isReset == true) {
      page = 1;
    } else {
      page++;
      sendTime = chatMessages.last.sentTime!;
    }
    List<ZtIMessageModel> newListData = await ZtImChartStore.to
        .getMessages(targetId: Get.arguments['userId'], sentTime: sendTime);

    isReset == true
        ? chatMessages.assignAll(newListData)
        : chatMessages.addAll(newListData);
    if (chatMessages.isNotEmpty) {
      ZtImChartStore.to.clearUnreadCount(
          chatMessages.first.conversationId!, chatMessages.first.sentTime!);
      if (isReset) {
        scrollToBottom();
      }
    }
    findFirstMessageInEveryDay();
    return newListData;
  }

  findFirstMessageInEveryDay() {
    if (chatMessages.isNotEmpty) {
      String lastDate = formatDate(
          DateTime.fromMillisecondsSinceEpoch(chatMessages.first.sentTime!),
          ['yyyy', '-', 'mm', '-', 'dd']);
      String today = formatDate(DateTime.now(), ['yyyy', '-', 'mm', '-', 'dd']);
      if (lastDate != today) {
        chatMessages.last.sentDay = lastDate;
      }
      bool ifAllToday = true;
      for (int i = 1; i < chatMessages.length; i++) {
        chatMessages[i].sentDay = null;
        String currentDate = formatDate(
            DateTime.fromMillisecondsSinceEpoch(chatMessages[i].sentTime!),
            ['yyyy', '-', 'mm', '-', 'dd']);
        if (lastDate != currentDate) {
          chatMessages[i].sentDay = currentDate;
          lastDate = currentDate;
          ifAllToday = false;
        }
      }
      if (ifAllToday) {
        chatMessages.last.sentDay = lastDate;
      }
    }
  }

  Future<void> reportComplain(String complainCategory) async {
    await ZtLoadingUtil.show(status: '$complainCategory...');
    try {
      final response = await Api.reportComplain(
          int.parse(Get.arguments['userId']),
          complainCategory,
          complainCategory == 'Report' ? 'Porngraphic' : '');
      if (complainCategory == 'Report') {
        ZtLogManger().putLog(
          ZtLogType.clickEvent,
          ZtLogManger.buildEvent(
              logType: ZtLogType.clickEvent, page: ZtLogPages.report),
        );
      } else {
        ZtLogManger().putLog(
          ZtLogType.clickEvent,
          ZtLogManger.buildEvent(
              logType: ZtLogType.clickEvent, page: ZtLogPages.block),
        );
      }

      if (response.data != null) {
        // 1.unfollowed
        if (complainCategory == 'Block') {
          ZtBlockStore.to.getBlockList();
          await Api.removeFriend(Get.arguments['userId']);
          // 2.unfollowed rank list in cache
          await ZtRankController().asyncRankListFollowed(
              Get.arguments['userId'], false); // 3.block user in IM
          await ZtImChartStore.to.addToBlacklist(Get.arguments['userId']);
          // 4.remove converstaion from IM
          ZtImChartStore.to.removeConversation(
              Get.arguments['userId'], RCIMIWConversationType.private);
        }
        Get.back<ZtBackActionEnum?>(result: ZtBackActionEnum.refresh);
        await ZtSnackBarUtil().success('$complainCategory Successfully.');
      }
    } catch (e) {
      ZtSnackBarUtil().error('$complainCategory faield $e');
    } finally {
      ZtLoadingUtil.dismiss();
    }
  }

  Future<void> sendMediaMessage(RCIMIWMessageType type) async {
    Get.focusScope?.unfocus();
    if (type == RCIMIWMessageType.voice) {
      await stopRecorder();
      voiceShowing.value = false;
      if (recordedTime <= 1) {
        ZtSnackBarUtil().warning('The voice duration is too short.');
        return;
      }
    }
    bool isSucceed = await ZtImChartStore.to.sendMediaMessage(
        userId: Get.arguments['userId'],
        path: uploadFilePath.value,
        duration: recordedTime,
        nickname: Get.arguments['nickname'],
        messageType: type,
        avatar: Get.arguments['avatar'],
        onMediaMessageSending: (RCIMIWMessage? message) async {
          /*  getData(isReset: true, policy: RCIMIWMessageOperationPolicy.local); */
        },
        onMessageSent: (RCIMIWMessage? message) async {
          if (message != null) {
            ZtIMessageModel sentMessage =
                ZtImChartStore.to.processImMessage(message);
            int index = chatMessages.indexWhere(
                (element) => element.messageId == sentMessage.messageId);
            if (index > -1) chatMessages[index] = sentMessage;
          }
          /*   getData(isReset: true); */
        },
        onMessageSaved: (RCIMIWMessage? message) async {
          if (message != null) {
            chatMessages.insert(0, ZtImChartStore.to.processImMessage(message));
          }
        });
    if (isSucceed) {
      uploadFilePath('');
      recordedTime = 0;
    }
  }

  Future<void> sendMessage() async {
    emojiShowing.value = false;
    Get.focusScope?.unfocus();
    if (textEditingController.text.isEmpty) {
      ZtSnackBarUtil().warning('Message cannot be empty.');
      return;
    }

    await ZtImChartStore.to.sendMessage(
        userId: Get.arguments['userId'],
        message: textEditingController.text,
        nickname: Get.arguments['nickname'],
        avatar: Get.arguments['avatar'],
        onMessageSent: (RCIMIWMessage? message) async {
          await Future.delayed(const Duration(milliseconds: 500));
          await getData(
              isReset: true, policy: RCIMIWMessageOperationPolicy.localRemote);
        },
        onMessageSaved: (RCIMIWMessage? message) async {
          await Future.delayed(const Duration(milliseconds: 500));
          await getData(isReset: true);
        });
    textEditingController.clear();
    // if (isSucceed) {}
  }

  Future<void> scrollToBottom() async {
    /*  WidgetsBinding.instance?.addPostFrameCallback((_) async {
      await Future.delayed(const Duration(milliseconds: 200));
      scrollController.jumpTo(scrollController.position.maxScrollExtent);
    }); */
  }

  Future<void> openMore() async {
    await Get.bottomSheet(CupertinoActionSheet(
      cancelButton: CupertinoActionSheetAction(
          onPressed: () {
            Get.back();
          },
          child: const Text(
            'Cancel',
            style: TextStyle(color: Color(0xffcdcdcd)),
          )),
      actions: ['Block', 'Report']
          .map((item) => CupertinoActionSheetAction(
              onPressed: () async {
                Get.back();
                reportComplain(item);
              },
              child: Text(
                item,
                style: const TextStyle(color: ZtAppTheme.primaryColor),
              )))
          .toList(),
    ));
  }

  Future<void> pickPhoto() async {
    XFile? pickedFile = await ZtMediaUtil().getImageFromGallery();
    if (pickedFile != null) {
      filePickerShowing.value = false;
      uploadFilePath(pickedFile.path);
      await sendMediaMessage(RCIMIWMessageType.image);
    }
  }

  Future<void> pickFile() async {
    FilePickerResult? result = await FilePicker.platform.pickFiles();
    if (result != null) {
      filePickerShowing.value = false;
      uploadFilePath(result.files.single.path);
      await sendMediaMessage(RCIMIWMessageType.file);
    }
  }

  void onPointUp(PointerUpEvent event) {
    if (voiceShowing.value == false || !mRecorder!.isRecording) {
      return;
    }
    RenderBox cancelContainerBox =
        containerCancelKey.currentContext?.findRenderObject() as RenderBox;
    Offset cancelContainerPosition =
        cancelContainerBox.localToGlobal(Offset.zero);
    Size cancelContainerSize = cancelContainerBox.size;
    bool cancelResut = event.position.dx >= cancelContainerPosition.dx &&
        event.position.dx <=
            cancelContainerPosition.dx + cancelContainerSize.width &&
        event.position.dy >= cancelContainerPosition.dy &&
        event.position.dy <=
            cancelContainerPosition.dy + cancelContainerSize.height;

    RenderBox sendBox =
        containerSendKey.currentContext?.findRenderObject() as RenderBox;
    Offset sendContainerPosition = sendBox.localToGlobal(Offset.zero);
    Size sendContainerSize = sendBox.size;
    bool sendResult = event.position.dx >= sendContainerPosition.dx &&
        event.position.dx <=
            sendContainerPosition.dx + sendContainerSize.width &&
        event.position.dy >= sendContainerPosition.dy &&
        event.position.dy <=
            sendContainerPosition.dy + sendContainerSize.height;
    isHoverOnSend.value = sendResult;

    if (cancelResut) {
      isHoverOnCancel.value = false;
      onCancelBtnPress();
    }
    if (sendResult) {
      isHoverOnSend.value = false;
      sendMediaMessage(RCIMIWMessageType.voice);
    }
  }

  void onCancelBtnPress() {
    stopPlayer();
    stopRecorder(forceStop: true);
    uploadFilePath('');
    recordedTime = 0;
    voiceShowing.value = false;
  }

  void onPointMove(event) {
    if (voiceShowing.value == false) {
      return;
    }
    RenderBox sendBox =
        containerSendKey.currentContext?.findRenderObject() as RenderBox;
    Offset sendContainerPosition = sendBox.localToGlobal(Offset.zero);
    Size sendContainerSize = sendBox.size;
    bool sendResult = event.position.dx >= sendContainerPosition.dx &&
        event.position.dx <=
            sendContainerPosition.dx + sendContainerSize.width &&
        event.position.dy >= sendContainerPosition.dy &&
        event.position.dy <=
            sendContainerPosition.dy + sendContainerSize.height;
    isHoverOnSend.value = sendResult;

    RenderBox cancelBox =
        containerCancelKey.currentContext?.findRenderObject() as RenderBox;
    Offset cancelContainerPosition = cancelBox.localToGlobal(Offset.zero);
    Size cancelContainerSize = cancelBox.size;
    bool cancelResult = event.position.dx >= cancelContainerPosition.dx &&
        event.position.dx <=
            cancelContainerPosition.dx + cancelContainerSize.width &&
        event.position.dy >= cancelContainerPosition.dy &&
        event.position.dy <=
            cancelContainerPosition.dy + cancelContainerSize.height;
    isHoverOnCancel.value = cancelResult;
  }

  Future<void> translateMessage(String message, int index) async {
    chatMessages[index].isTranslating = true;
    chatMessages.refresh();
    await ZtTranslateService.to.googleTranslate(message, (value) {
      chatMessages[index].translatedText = value;
      chatMessages[index].isTranslating = false;
      chatMessages.refresh();
      if (index == chatMessages.length - 1) {
        scrollToBottom();
      }
    });
  }
}
